import {
  PipeTransform,
  Injectable,
  ArgumentMetadata,
  BadRequestException,
} from '@nestjs/common';
import { validate, ValidationError } from 'class-validator';
import { plainToClass } from 'class-transformer';

const flatObjectValues = (obj: any): string => {
  const keys: string[] = Object.getOwnPropertyNames(obj);
  const res: any = [];
  let key: string;
  for (key of keys) {
    res.push(obj[key]);
  }
  return res.join(',');
};

@Injectable()
export class ApiValidationPipe implements PipeTransform<any> {
  async transform(value: any, metadata: ArgumentMetadata) {
    const { metatype } = metadata;

    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }

    const object = plainToClass(metatype, value);
    const errors = await validate(object);

    const messages: any[] = [];
    let exception: ValidationError | any;
    let child: ValidationError | any;
    let childNested: ValidationError | any;

    if (errors.length > 0) {
      for (exception of errors) {
        if (exception.constraints) {
          messages.push(flatObjectValues(exception.constraints));
        }
        if (Array.isArray(exception.children)) {
          for (child of exception.children) {
            if (child.constraints) {
              messages.push(flatObjectValues(child.constraints));
            }

            if (Array.isArray(child.children)) {
              for (childNested of child.children) {
                messages.push(flatObjectValues(childNested.constraints));
              }
            }
          }
        }
      }
      throw new BadRequestException(messages[0]);
    }
    return value;
  }

  private toValidate(metatype: any): boolean {
    const types = [String, Boolean, Number, Date, Array, Object];
    return !types.find((type) => metatype === type);
  }
}
